package com.songlanyun.modules.cnarea.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.songlanyun.common.utils.StrUtil;
import com.songlanyun.common.utils.custom.ClassConvertUtils;
import com.songlanyun.common.utils.custom.HttpUtils;
import com.songlanyun.modules.cnarea.dao.CnAreaDao;
import com.songlanyun.modules.cnarea.entity.CnArea;
import com.songlanyun.modules.cnarea.service.CnAreaService;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;


@Service("cnAreaService")
public class CnAreaServiceImpl extends ServiceImpl<CnAreaDao, CnArea> implements CnAreaService {

    @Value("${txlbs.host}")
    private String host;

    @Value("${txlbs.key}")
    private String key;

    @Value("${txlbs.get-all-url}")
    private String getAllUrl;
    @Value("${txlbs.getchildren}")
    private String getchildren;
    @Value("${txlbs.get-geocoder-url}")
    private String getCodeUrl;

    @Value("${txlbs.redis-key.hash}")
    private String lbsHashKey;

    @Value("${txlbs.redis-key.tree}")
    private String lbsTreeKey;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    /**
     * 刷新腾讯行政区域地址到本地，并保存到缓存
     * 缓存时是用 redis hashMap的结构保存 , lbsHashKey :lida:tx:lbs
     * Map<String,List<CnArea>>的数据结构保存，键是父级id,值是List<CnArea>
     * 键: 420100   (是武汉的行政Code)
     * 值：例如 [
     * {
     * "id": 1967,
     * "name": "江岸区",
     * "code": "420102",
     * "parent": "420100",
     * "longitude": "114.3096",
     * "latitude": "30.59982",
     * "level": 3
     * },
     * {
     * "id": 1968,
     * "name": "江汉区",
     * "code": "420103",
     * "parent": "420100",
     * "longitude": "114.27096",
     * "latitude": "30.60147",
     * "level": 3
     * },
     * {
     * "id": 1969,
     * "name": "硚口区",
     * "code": "420104",
     * "parent": "420100",
     * "longitude": "114.21498",
     * "latitude": "30.58145",
     * "level": 3
     * },
     * {
     * "id": 1970,
     * "name": "汉阳区",
     * "code": "420105",
     * "parent": "420100",
     * "longitude": "114.21772",
     * "latitude": "30.55473",
     * "level": 3
     * },
     * {
     * "id": 1971,
     * "name": "武昌区",
     * "code": "420106",
     * "parent": "420100",
     * "longitude": "114.31599",
     * "latitude": "30.55386",
     * "level": 3
     * },
     * {
     * "id": 1972,
     * "name": "青山区",
     * "code": "420107",
     * "parent": "420100",
     * "longitude": "114.38415",
     * "latitude": "30.64039",
     * "level": 3
     * },
     * {
     * "id": 1973,
     * "name": "洪山区",
     * "code": "420111",
     * "parent": "420100",
     * "longitude": "114.34253",
     * "latitude": "30.49984",
     * "level": 3
     * },
     * {
     * "id": 1974,
     * "name": "东西湖区",
     * "code": "420112",
     * "parent": "420100",
     * "longitude": "114.13702",
     * "latitude": "30.61996",
     * "level": 3
     * },
     * {
     * "id": 1975,
     * "name": "汉南区",
     * "code": "420113",
     * "parent": "420100",
     * "longitude": "114.08469",
     * "latitude": "30.30895",
     * "level": 3
     * },
     * {
     * "id": 1976,
     * "name": "蔡甸区",
     * "code": "420114",
     * "parent": "420100",
     * "longitude": "114.02919",
     * "latitude": "30.58203",
     * "level": 3
     * },
     * {
     * "id": 1977,
     * "name": "江夏区",
     * "code": "420115",
     * "parent": "420100",
     * "longitude": "114.32168",
     * "latitude": "30.37559",
     * "level": 3
     * },
     * {
     * "id": 1978,
     * "name": "黄陂区",
     * "code": "420116",
     * "parent": "420100",
     * "longitude": "114.37509",
     * "latitude": "30.88131",
     * "level": 3
     * },
     * {
     * "id": 1979,
     * "name": "新洲区",
     * "code": "420117",
     * "parent": "420100",
     * "longitude": "114.80114",
     * "latitude": "30.84154",
     * "level": 3
     * }
     * ]
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refresh() {
        List<CnArea> province = new ArrayList<>();
        List<CnArea> city = new ArrayList<>();
        List<CnArea> area = new ArrayList<>();
        List<CnArea> street = new ArrayList<>();
        JSONArray array = getTXLbsData();
        JSONArray s1 = array.getJSONArray(0);
        JSONArray s2 = array.getJSONArray(1);
        JSONArray s3 = array.getJSONArray(2);

        if (s1.size() == 0 || s2.size() == 0 || s3.size() == 0) {
            return;
        }

        this.baseMapper.truncate();
        JSONObject js = new JSONObject();
        CnArea cnArea = null;
        CnArea cnAreaC = null;

        for (int i = 0; i < s3.size(); i++) {
            js = s3.getJSONObject(i);
            cnArea = process(js, 3);
            cnArea.setParent(getCode(cnArea.getValue(), 3));
            area.add(cnArea);

            JSONArray  jsonArray = this.getLbsChildrenData(cnArea.getValue());
            if(!ObjectUtil.isEmpty(jsonArray))
            {
                jsonArray = jsonArray.getJSONArray(0);
                for (int j = 0; j < jsonArray.size(); j++)
                {
                    JSONObject jsonObject = jsonArray.getJSONObject(j);
                    cnAreaC= process(jsonObject, 4);
                    cnAreaC.setParent(cnArea.getValue());
                    street.add(cnAreaC);
                }

            }
        }

        for (int i = 0; i < s2.size(); i++) {
            js = s2.getJSONObject(i);
            cnArea = process(js, 2);
            cnArea.setParent(getCode(cnArea.getValue(), 2));
            city.add(cnArea);

            if(!isHasChild(area,cnArea))
            {
                JSONArray  jsonArray = this.getLbsChildrenData(cnArea.getValue());
                if(!ObjectUtil.isEmpty(jsonArray))
                {
                    jsonArray = jsonArray.getJSONArray(0);
                    for (int j = 0; j < jsonArray.size(); j++)
                    {
                        JSONObject jsonObject = jsonArray.getJSONObject(j);
                        cnAreaC= process(jsonObject, 3);
                        cnAreaC.setParent(cnArea.getValue());
                        street.add(cnAreaC);
                    }

                }
            }
        }


        for (int i = 0; i < s1.size(); i++) {
            js = s1.getJSONObject(i);
            cnArea = process(js, 1);
            cnArea.setParent(CnArea.CN.getValue());
            province.add(cnArea);
        }


        this.saveBatch(province);
        this.saveBatch(city);
        this.saveBatch(area);
        this.saveBatch(street);
        redisTemplate.delete(lbsHashKey);
        /**
         * 以hash map的方式缓存
         */
        //this.getALLLbs();
        redisTemplate.delete(lbsTreeKey);
        /**
         * 以tree方式缓存
         */
        //this.cacheTree();
    }

    private Boolean isHasChild(List<CnArea> cnAreas,CnArea current)
    {
        for(CnArea i :cnAreas)
        {
            if(i.getParent().equals(current.getValue()))
            {
                return true;
            }
        }
        return false;

    }

    @Override
    public Map<String, List<CnArea>> getALLLbs() {
        Map<String, List<CnArea>> lbs = this.getAllLbs();
        if (lbs == null) {
            List<CnArea> list = this.list(new LambdaQueryWrapper<CnArea>().orderByAsc(CnArea::getLevel));
            Map<String, List<CnArea>> collect = list.stream().collect(Collectors.groupingBy(CnArea::getParent));
            BoundHashOperations<String, String, Object> operations = getRedisLbs();
            collect.entrySet().stream().forEach(o -> {
                operations.put(o.getKey(), JSONUtil.toJsonPrettyStr(o.getValue()));
            });
            return collect;
        }
        return lbs;
    }

    @Override
    public List<CnArea> getChildren(String parentCode) {
        return this.baseMapper.selectList(new LambdaQueryWrapper<CnArea>().eq(CnArea::getParent,parentCode));
    }

    private void cacheTree() {
        List<CnArea> all = this.baseMapper.selectList(new LambdaQueryWrapper<CnArea>().lt(CnArea::getLevel, 4)
                .orderByAsc(CnArea::getParent));
        Map<String, List<CnArea>> collect = all.stream().collect(Collectors.groupingBy(CnArea::getParent));

        List<CnArea> first = collect.get(CnArea.CN.getValue());
        first.stream().forEach(o -> {
            List<CnArea> two = collect.get(o.getValue());
            if (CollectionUtil.isNotEmpty(two)) {
                two.stream().forEach(t -> {
                    List<CnArea> three = collect.get(t.getValue());
                    if (CollectionUtil.isNotEmpty(three)) {
                        t.setChildren(three);
                    }
                });
                o.setChildren(two);
            }
        });
        CnArea.CN.setChildren(first);
        redisTemplate.opsForValue().set(lbsTreeKey, JSONUtil.toJsonStr(CnArea.CN));
    }

    @Override
    public JSONObject getTreeLbs() {

        String treeStr = redisTemplate.opsForValue().get(lbsTreeKey);
        if (treeStr == null || treeStr == "") {
            this.cacheTree();
            treeStr = redisTemplate.opsForValue().get(lbsTreeKey);
        }

        return JSON.parseObject(treeStr);
    }




    @Override
    public List<CnArea> getPath(String code) {
        List<CnArea> path = new ArrayList<>();
        while (true) {
            CnArea current = this.getByCode(code);
            if (current != null) {
                path.add(current);
                if (!current.getParent().equals(CnArea.CN.getValue())) {
                    code = current.getParent();
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        Collections.reverse(path);
        return path;
    }

    @Override
    public CnArea getByCode(String code) {
        return this.getOne(new LambdaQueryWrapper<CnArea>().eq(CnArea::getValue, code).last("limit 1"));

    }

    @Override
    public String getCodeByName(String name, String parent) {
        if (StringUtils.isNotBlank(name)) {
            CnArea cnArea = this.baseMapper.selectOne(
                    new LambdaQueryWrapper<CnArea>()
                            .eq(CnArea::getLabel, name)
                            .eq(StringUtils.isNotBlank(parent), CnArea::getParent, parent)
            );
            if (cnArea != null) {
                return cnArea.getValue();
            }
        }

        return null;
    }

    @Override
    public JSONObject getAddress(String location) {

        HashMap<String, String> query = new HashMap<>();
        query.put("key", key);
        query.put("location", location);

        try {
            HttpResponse response = HttpUtils.doGet(host, getCodeUrl, new HashMap<String, String>(), query);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 微博信息查询成功
                JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
                JSONObject result = jsonObject.getJSONObject("result");
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public JSONObject getCode(String address) {
        HashMap<String, String> query = new HashMap<>();
        query.put("key", key);
        query.put("address", address);

        try {
            HttpResponse response = HttpUtils.doGet(host, getCodeUrl, new HashMap<String, String>(), query);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 微博信息查询成功
                JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
                JSONObject result = jsonObject.getJSONObject("result");
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public CnArea getTreeLbsByCity() {
        String treeStr = redisTemplate.opsForValue().get(lbsTreeKey);
        if (treeStr == null || treeStr == "") {
            this.getALLLbs();
            this.cacheTree();
            treeStr = redisTemplate.opsForValue().get(lbsTreeKey);
        }

        List<String> zsCitys = Arrays.asList("110000", "120000", "500000", "310000");
        CnArea root = mapObj(treeStr);
        if (root != null) {
            List<CnArea> pList = root.getChildren();
            pList.forEach(p -> {
                if (zsCitys.contains(p.getValue())) {
                    p.setChildren(new ArrayList<>());
                } else {
                    List<CnArea> children = p.getChildren();
                    children.forEach(o -> {
                        o.setChildren(new ArrayList<>());
                    });
                }
            });
        }
        return root;
    }

    @Override
    public CnArea getCnAreaByName(String name) {
        CnArea cnArea = this.baseMapper.selectOne(
                new LambdaQueryWrapper<CnArea>()
                        .like(CnArea::getLabel, name)
                .last("limit 1")
        );

        return cnArea;
    }

    @Override
    public void refreshJson() {
        try {
            String provinces = readFileToStr("D:\\工作\\所有项目\\残联绿码\\admin\\lida_system\\src\\main\\resources\\cnarea\\provinces.json");
            JSONArray jsonArray = JSON.parseArray(provinces);
            List<CnArea> pl = new ArrayList<>();
            for (int i = 0; i < jsonArray.size(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                CnArea pa = new CnArea();
                pa.setLabel(jsonObject.getString("name"));
                pa.setValue(StrUtil.addZeroForStr(jsonObject.getString("code"),6,2));
                pa.setParent("100000");
                pa.setLevel(1);
                pl.add(pa);
            }
            this.saveBatch(pl);

            String cities = readFileToStr("D:\\工作\\所有项目\\残联绿码\\admin\\lida_system\\src\\main\\resources\\cnarea\\cities.json");
            jsonArray = JSON.parseArray(cities);
            List<CnArea> cl = new ArrayList<>();
            for (int i = 0; i < jsonArray.size(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                CnArea ca = new CnArea();
                ca.setLabel(jsonObject.getString("name"));
                ca.setValue(StrUtil.addZeroForStr(jsonObject.getString("code"),6,2));
                ca.setParent(StrUtil.addZeroForStr(jsonObject.getString("provinceCode"),6,2));
                ca.setLevel(2);
                cl.add(ca);
            }
            this.saveBatch(cl);


            String areas = readFileToStr("D:\\工作\\所有项目\\残联绿码\\admin\\lida_system\\src\\main\\resources\\cnarea\\areas.json");
            jsonArray = JSON.parseArray(areas);
            List<CnArea> al = new ArrayList<>();
            for (int i = 0; i < jsonArray.size(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                CnArea aa = new CnArea();
                aa.setLabel(jsonObject.getString("name"));
                aa.setValue(StrUtil.addZeroForStr(jsonObject.getString("code"),6,2));
                aa.setParent(StrUtil.addZeroForStr(jsonObject.getString("cityCode"),6,2));
                aa.setLevel(3);
                al.add(aa);
            }
            this.saveBatch(al);


            String streets = readFileToStr("D:\\工作\\所有项目\\残联绿码\\admin\\lida_system\\src\\main\\resources\\cnarea\\streets.json");
            jsonArray = JSON.parseArray(streets);
            List<CnArea> sl = new ArrayList<>();
            for (int i = 0; i < jsonArray.size(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                CnArea sa = new CnArea();
                sa.setLabel(jsonObject.getString("name"));
                sa.setValue(StrUtil.addZeroForStr(jsonObject.getString("code"),6,2));
                sa.setParent(StrUtil.addZeroForStr(jsonObject.getString("areaCode"),6,2));
                sa.setLevel(4);
                sl.add(sa);
            }
            this.saveBatch(sl,1000);


            String villages = readFileToStr("D:\\工作\\所有项目\\残联绿码\\admin\\lida_system\\src\\main\\resources\\cnarea\\villages.json");
            jsonArray = JSON.parseArray(villages);
            List<CnArea> vl = new ArrayList<>();
            for (int i = 0; i < jsonArray.size(); i++)
            {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                CnArea va = new CnArea();
                va.setLabel(jsonObject.getString("name"));
                va.setValue(StrUtil.addZeroForStr(jsonObject.getString("code"),6,2));
                va.setParent(StrUtil.addZeroForStr(jsonObject.getString("streetCode"),6,2));
                va.setLevel(5);
                vl.add(va);
            }
            this.saveBatch(vl,1000);


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String readFileToStr(String filePath) throws IOException {
        File file = new File(filePath);
        FileReader fileReader = new FileReader(file);
        Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
        int ch = 0;
        StringBuilder sb = new StringBuilder();
        while ((ch = reader.read()) != -1) {
            sb.append((char) ch);
        }
        fileReader.close();
        reader.close();
        return  sb.toString();
    }


    /**
     * 请求腾训行政区域
     *
     * @return
     */
    @Override
    public JSONArray getTXLbsData() {
        HashMap<String, String> query = new HashMap<>();
        query.put("key", key);
        try {
            HttpResponse response = HttpUtils.doGet(host, getAllUrl, new HashMap<String, String>(), query);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 微博信息查询成功
                JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
                JSONArray array = jsonObject.getJSONArray("result");
                return array;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public JSONArray getLbsChild(String code) {
        return this.getLbsChildrenData(code);
    }

    private JSONArray getLbsChildrenData(String adcode) {
        HashMap<String, String> query = new HashMap<>();
        query.put("key", key);
        query.put("id",adcode);
        try {
            HttpResponse response = HttpUtils.doGet(host, getchildren, new HashMap<String, String>(), query);
            if (response.getStatusLine().getStatusCode() == 200) {
                // 微博信息查询成功
                JSONObject jsonObject = JSON.parseObject(EntityUtils.toString(response.getEntity()));
                JSONArray array = jsonObject.getJSONArray("result");
                return array;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    private List<CnArea> mapList(String value) {
        try {
            return ClassConvertUtils.stringToClassSnake(value, new TypeReference<List<CnArea>>() {
            });
        } catch (IOException e) {
            return new ArrayList<>();
        }
    }

    private CnArea mapObj(String value) {
        try {
            return ClassConvertUtils.stringToClassSnake(value, new TypeReference<CnArea>() {
            });
        } catch (IOException e) {
            return null;
        }
    }


    private Map<String, List<CnArea>> getAllLbs() {
        BoundHashOperations<String, String, Object> operations = this.getRedisLbs();
        Map<String, Object> entries = operations.entries();
        if (entries != null && !entries.isEmpty() && entries.size() > 0) {
            Map<String, List<CnArea>> collect = entries.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
                    o -> mapList((String) o.getValue())
            ));
            return collect;
        }
        return null;
    }

    private BoundHashOperations<String, String, Object> getRedisLbs() {
        BoundHashOperations<String, String, Object> operations = redisTemplate.boundHashOps(lbsHashKey);
        return operations;
    }


    private CnArea process(JSONObject js, Integer level) {
        CnArea cnArea = new CnArea();
        cnArea.setValue(js.get("id").toString());
        cnArea.setLevel(level);
        cnArea.setLabel(js.get("fullname").toString());
        JSONObject location = js.getJSONObject("location");
        cnArea.setLongitude(location.get("lng").toString());
        cnArea.setLatitude(location.get("lat").toString());
        return cnArea;
    }


    private String getCode(String code, int level) {
        int prefix = (level - 1) * 2;
        String left = code.substring(0, prefix);
        return StringUtils.rightPad(left, 6, "0");
    }


}
